#include <QtWidgets>

#include "diagramwindow.h"
#include "link.h"
#include "node.h"
#include "propertiesdialog.h"
#include "bfsalgorithm.h"
#include "chart.h"

#include <QFileDialog>
#include <QXmlStreamReader>

DiagramWindow::DiagramWindow()
{
    m_pScene = new QGraphicsScene(0, 0, 600, 500);

    m_pView = new QGraphicsView;
    m_pView->setScene(m_pScene);
    m_pView->setDragMode(QGraphicsView::RubberBandDrag);
    m_pView->setRenderHints(QPainter::Antialiasing
                         | QPainter::TextAntialiasing);
    m_pView->setContextMenuPolicy(Qt::ActionsContextMenu);
    setCentralWidget(m_pView);

    m_iMinZ = 0;
    m_iMaxZ = 0;
    m_iSeqNumber = 0;

    createActions();
    createMenus();
    createToolBars();

    connect(m_pScene, SIGNAL(selectionChanged()),
            this, SLOT(updateActions()));

    setWindowTitle(tr("Diagram"));
    updateActions();

    m_nodeIDHash.clear();
}

void DiagramWindow::addNode()
{
    Node *node = new Node(m_iSeqNumber);
    m_nodeIDHash.insert(m_iSeqNumber, node);
    node->setText(tr("Node %1").arg(m_iSeqNumber + 1));
    setupNode(node);
}

void DiagramWindow::addLink()
{
    NodePair nodes = selectedNodePair();
    if (nodes == NodePair())
        return;

    Link *link = new Link(nodes.first, nodes.second);
    m_pScene->addItem(link);
}

void DiagramWindow::del()
{
    QList<QGraphicsItem *> items = m_pScene->selectedItems();
    QMutableListIterator<QGraphicsItem *> i(items);
    while (i.hasNext()) {
        Link *link = dynamic_cast<Link *>(i.next());
        if (link) {
            delete link;
            i.remove();
        }
    }

    qDeleteAll(items);
}

void DiagramWindow::cut()
{
    Node *node = selectedNode();
    if (!node)
        return;

    copy();
    delete node;
}

void DiagramWindow::copy()
{
    Node *node = selectedNode();
    if (!node)
        return;

    QString str = QString("Node %1 %2 %3 %4")
                  .arg(node->textColor().name())
                  .arg(node->outlineColor().name())
                  .arg(node->backgroundColor().name())
                  .arg(node->text());
    QApplication::clipboard()->setText(str);
}

void DiagramWindow::paste()
{
    QString str = QApplication::clipboard()->text();
    QStringList parts = str.split(" ");

    if (parts.count() >= 5 && parts.first() == "Node")
    {
        Node *node = new Node(m_iSeqNumber);
        node->setText(QStringList(parts.mid(4)).join(" "));
        node->setTextColor(QColor(parts[1]));
        node->setOutlineColor(QColor(parts[2]));
        node->setBackgroundColor(QColor(parts[3]));
        setupNode(node);
    }
}

void DiagramWindow::bringToFront()
{
    ++m_iMaxZ;
    setZValue(m_iMaxZ);
}

void DiagramWindow::sendToBack()
{
    --m_iMinZ;
    setZValue(m_iMinZ);
}

void DiagramWindow::properties()
{
    Node *node = selectedNode();
    Link *link = selectedLink();

    if (node)
    {
        PropertiesDialog dialog(node, this);
        dialog.exec();
    }
    else if (link)
    {
        QColor color = QColorDialog::getColor(link->color(), this);
        if (color.isValid())
            link->setColor(color);
    }
}

void DiagramWindow::updateActions()
{
    bool hasSelection = !m_pScene->selectedItems().isEmpty();
    bool isNode = (selectedNode() != 0);
    bool isNodePair = (selectedNodePair() != NodePair());

    m_pCutAction->setEnabled(isNode);
    m_pCopyAction->setEnabled(isNode);
    m_pAddLinkAction->setEnabled(isNodePair);
    m_pDeleteAction->setEnabled(hasSelection);
    m_pBringToFrontAction->setEnabled(isNode);
    m_pSendToBackAction->setEnabled(isNode);
    m_pPropertiesAction->setEnabled(isNode);
    m_pBFSAction->setEnabled(isNodePair);

    foreach (QAction *action, m_pView->actions())
    {
        m_pView->removeAction(action);
    }

    foreach (QAction *action, m_pEditMenu->actions())
    {
        if (action->isEnabled())
            m_pView->addAction(action);
    }

    if(m_pBFSAction->isEnabled())
    {
        m_pView->addAction(m_pBFSAction);
    }

}

void DiagramWindow::slotSaveFile()
{
    QString strFileName = QFileDialog::getSaveFileName(this,
                               tr("Save File"),
                               ".",
                               "(xml(*.xml))");
    if(strFileName.isEmpty())
    {
        return;
    }
    qDebug() << "Save file: " << strFileName;

    QFile xmlFile(strFileName);
    if(!xmlFile.open(QFile::WriteOnly | QFile::Text))
    {
        return;
    }

    QXmlStreamWriter xmlWriter(&xmlFile);
    xmlWriter.setAutoFormatting(true);
    xmlWriter.writeStartDocument();
    xmlWriter.writeStartElement("Diagram");

    QList<QGraphicsItem *> itemList = m_pScene->items();
    foreach(QGraphicsItem *pItem, itemList)
    {
        if(Chart::NodeType == pItem->type())
        {
            Node *pNode = qgraphicsitem_cast<Node *>(pItem);
            if(NULL != pNode)
            {
                xmlWriter.writeStartElement("Node");
                xmlWriter.writeAttribute("id",
                                         QString::number(pNode->id()));
                xmlWriter.writeAttribute("x",
                                         QString::number(pNode->pos().x()));
                xmlWriter.writeAttribute("y",
                                         QString::number(pNode->pos().y()));
                xmlWriter.writeAttribute("text", pNode->text());
                xmlWriter.writeEndElement();
            }
        }


        if(Chart::LinkType == pItem->type())
        {
            Link *pLink = qgraphicsitem_cast<Link *>(pItem);
            if(NULL != pLink)
            {
                Node *pFromNode = pLink->fromNode();
                Node *pToNode = pLink->toNode();
                xmlWriter.writeStartElement("Link");
                xmlWriter.writeAttribute("from",
                                         QString::number(pFromNode->id()));
                xmlWriter.writeAttribute("to",
                                         QString::number(pToNode->id()));
                xmlWriter.writeEndElement();
            }
        }
    }

    xmlWriter.writeEndElement();
    xmlWriter.writeEndDocument();
    xmlFile.close();
}

void DiagramWindow::slotOpenFile()
{
    QString strFileName = QFileDialog::getOpenFileName(this,
                            tr("Open File"),
                            ".", "xml(*.xml)");
    if(strFileName.isEmpty())
    {
        return;
    }
    qDebug() << "Open file: " << strFileName;

    QFile xmlFile(strFileName);
    if(!xmlFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug() << "Open file fail!";
        return;
    }

    m_pScene->clear();
    m_nodeIDHash.clear();

    QXmlStreamReader xmlReader(&xmlFile);
    while(!xmlReader.atEnd())
    {
        xmlReader.readNext();
        if(xmlReader.isStartElement())
        {
            if(xmlReader.name() == "Node")
            {
                QXmlStreamAttributes attributes = xmlReader.attributes();
                if(!attributes.hasAttribute("id")
                        || !attributes.hasAttribute("x")
                        || !attributes.hasAttribute("y")
                        || !attributes.hasAttribute("text"))
                {
                    continue;
                }
                int iID = attributes.value("id").toInt();
                int iX = attributes.value("x").toInt();
                int iY = attributes.value("y").toInt();
                QString strText = attributes.value("text").toString();
                addNode(iID, QPoint(iX, iY), strText);
            }

            if(xmlReader.name() == "Link")
            {
                QXmlStreamAttributes attributes = xmlReader.attributes();
                if(!attributes.hasAttribute("from")
                    || !attributes.hasAttribute("to"))
                {
                    continue;
                }

                int iFromID = attributes.value("from").toInt();
                int iToID = attributes.value("to").toInt();
                addLink(iFromID, iToID);

            }
        }

    }

    if(xmlReader.hasError())
    {
        qDebug() << "[" << __FUNCTION__ << "]" << xmlReader.errorString();
    }

    xmlFile.close();
}

void DiagramWindow::slotBFS()
{
    qDebug() << "[" << __FUNCTION__ << "]";
    NodePair nodePair = selectedNodePair();
    BFSAlgorithm().printPath(m_pScene, nodePair.first, nodePair.second);
}

void DiagramWindow::addNode(int iID, QPoint point, QString text)
{
    Node *node = new Node(iID);
    m_nodeIDHash.insert(iID, node);
    node->setText(text);
    node->setPos(point);

    m_pScene->addItem(node);

    m_pScene->clearSelection();
    node->setSelected(true);
    bringToFront();

    if(m_iSeqNumber < iID)
    {
        m_iSeqNumber = iID + 1;
    }
}

void DiagramWindow::addLink(int iFromID, int iToID)
{
    Node *pFromNode = m_nodeIDHash.value(iFromID);
    if(NULL == pFromNode)
    {
        return;
    }

    Node *pToNode = m_nodeIDHash.value(iToID);
    if(NULL == pToNode)
    {
        return;
    }

    Link *link = new Link(pFromNode, pToNode);
    m_pScene->addItem(link);
}

void DiagramWindow::createActions()
{
    m_pOpenAction = new QAction("Open", this);
    m_pOpenAction->setShortcut(tr("Ctrl+O"));
    connect(m_pOpenAction, SIGNAL(triggered(bool)),
            this, SLOT(slotOpenFile()));

    m_pSaveAction = new QAction("Save", this);
    m_pSaveAction->setShortcut(tr("Ctrl+S"));
    connect(m_pSaveAction, SIGNAL(triggered(bool)),
            this, SLOT(slotSaveFile()));

    m_pExitAction = new QAction(tr("E&xit"), this);
    m_pExitAction->setShortcut(tr("Ctrl+Q"));
    connect(m_pExitAction, SIGNAL(triggered()), this, SLOT(close()));

    m_pAddNodeAction = new QAction(tr("Add &Node"), this);
    m_pAddNodeAction->setIcon(QIcon(":/images/node.png"));
    m_pAddNodeAction->setShortcut(tr("Ctrl+N"));
    connect(m_pAddNodeAction, SIGNAL(triggered()), this, SLOT(addNode()));

    m_pAddLinkAction = new QAction(tr("Add &Link"), this);
    m_pAddLinkAction->setIcon(QIcon(":/images/link.png"));
    m_pAddLinkAction->setShortcut(tr("Ctrl+L"));
    connect(m_pAddLinkAction, SIGNAL(triggered()), this, SLOT(addLink()));

    m_pDeleteAction = new QAction(tr("&Delete"), this);
    m_pDeleteAction->setIcon(QIcon(":/images/delete.png"));
    m_pDeleteAction->setShortcut(tr("Del"));
    connect(m_pDeleteAction, SIGNAL(triggered()), this, SLOT(del()));

    m_pCutAction = new QAction(tr("Cu&t"), this);
    m_pCutAction->setIcon(QIcon(":/images/cut.png"));
    m_pCutAction->setShortcut(tr("Ctrl+X"));
    connect(m_pCutAction, SIGNAL(triggered()), this, SLOT(cut()));

    m_pCopyAction = new QAction(tr("&Copy"), this);
    m_pCopyAction->setIcon(QIcon(":/images/copy.png"));
    m_pCopyAction->setShortcut(tr("Ctrl+C"));
    connect(m_pCopyAction, SIGNAL(triggered()), this, SLOT(copy()));

    m_pPasteAction = new QAction(tr("&Paste"), this);
    m_pPasteAction->setIcon(QIcon(":/images/paste.png"));
    m_pPasteAction->setShortcut(tr("Ctrl+V"));
    connect(m_pPasteAction, SIGNAL(triggered()), this, SLOT(paste()));

    m_pBringToFrontAction = new QAction(tr("Bring to &Front"), this);
    m_pBringToFrontAction->setIcon(QIcon(":/images/bringtofront.png"));
    connect(m_pBringToFrontAction, SIGNAL(triggered()),
            this, SLOT(bringToFront()));

    m_pSendToBackAction = new QAction(tr("&Send to Back"), this);
    m_pSendToBackAction->setIcon(QIcon(":/images/sendtoback.png"));
    connect(m_pSendToBackAction, SIGNAL(triggered()),
            this, SLOT(sendToBack()));

    m_pPropertiesAction = new QAction(tr("P&roperties..."), this);
    connect(m_pPropertiesAction, SIGNAL(triggered()),
            this, SLOT(properties()));

    m_pBFSAction = new QAction("BFS", this);
    connect(m_pBFSAction, SIGNAL(triggered(bool)),
            this, SLOT(slotBFS()));
}

void DiagramWindow::createMenus()
{
    m_pFileMenu = menuBar()->addMenu(tr("&File"));
    m_pFileMenu->addAction(m_pOpenAction);
    m_pFileMenu->addAction(m_pSaveAction);
    m_pFileMenu->addSeparator();
    m_pFileMenu->addAction(m_pExitAction);

    m_pEditMenu = menuBar()->addMenu(tr("&Edit"));
    m_pEditMenu->addAction(m_pAddNodeAction);
    m_pEditMenu->addAction(m_pAddLinkAction);
    m_pEditMenu->addAction(m_pDeleteAction);
    m_pEditMenu->addSeparator();
    m_pEditMenu->addAction(m_pCutAction);
    m_pEditMenu->addAction(m_pCopyAction);
    m_pEditMenu->addAction(m_pPasteAction);
    m_pEditMenu->addSeparator();
    m_pEditMenu->addAction(m_pBringToFrontAction);
    m_pEditMenu->addAction(m_pSendToBackAction);
    m_pEditMenu->addSeparator();
    m_pEditMenu->addAction(m_pPropertiesAction);

    m_pSearchMenu = menuBar()->addMenu("Search");
    m_pSearchMenu->addAction(m_pBFSAction);
}

void DiagramWindow::createToolBars()
{
    m_pEditToolBar = addToolBar(tr("Edit"));
    m_pEditToolBar->addAction(m_pAddNodeAction);
    m_pEditToolBar->addAction(m_pAddLinkAction);
    m_pEditToolBar->addAction(m_pDeleteAction);
    m_pEditToolBar->addSeparator();
    m_pEditToolBar->addAction(m_pCutAction);
    m_pEditToolBar->addAction(m_pCopyAction);
    m_pEditToolBar->addAction(m_pPasteAction);
    m_pEditToolBar->addSeparator();
    m_pEditToolBar->addAction(m_pBringToFrontAction);
    m_pEditToolBar->addAction(m_pSendToBackAction);
//    m_pEditToolBar->addSeparator();
//    m_pEditToolBar->addAction(m_pBFSAction);
}

void DiagramWindow::setZValue(int z)
{
    Node *node = selectedNode();
    if (node)
        node->setZValue(z);
}

void DiagramWindow::setupNode(Node *node)
{
    node->setPos(QPoint(80 + (100 * (m_iSeqNumber % 5)),
                        80 + (50 * ((m_iSeqNumber / 5) % 7))));
    m_pScene->addItem(node);

    m_pScene->clearSelection();
    node->setSelected(true);
    bringToFront();
}

Node *DiagramWindow::selectedNode() const
{
    QList<QGraphicsItem *> items = m_pScene->selectedItems();
    if (items.count() == 1) {
        return dynamic_cast<Node *>(items.first());
    } else {
        return 0;
    }
}

Link *DiagramWindow::selectedLink() const
{
    QList<QGraphicsItem *> items = m_pScene->selectedItems();
    if (items.count() == 1) {
        return dynamic_cast<Link *>(items.first());
    } else {
        return 0;
    }
}

DiagramWindow::NodePair DiagramWindow::selectedNodePair() const
{
    QList<QGraphicsItem *> items = m_pScene->selectedItems();
    if (items.count() == 2) {
        Node *first = dynamic_cast<Node *>(items.first());
        Node *second = dynamic_cast<Node *>(items.last());
        if (first && second)
            return NodePair(first, second);
    }
    return NodePair();
}
